Skip to content

[actor] Introduce Actor SDK#3077

Open
clabby wants to merge 21 commits into
mainfrom
cl/actor
Open

[actor] Introduce Actor SDK#3077
clabby wants to merge 21 commits into
mainfrom
cl/actor

Conversation

@clabby
Copy link
Copy Markdown
Collaborator

@clabby clabby commented Feb 8, 2026

Overview

Introduces an SDK for writing actor services with commonware-runtime.

related #1110

@clabby clabby self-assigned this Feb 8, 2026
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Feb 8, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
commonware-mcp d27bb8d Mar 10 2026, 07:04 PM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Feb 8, 2026

Deploying monorepo with  Cloudflare Pages  Cloudflare Pages

Latest commit: d27bb8d
Status: ✅  Deploy successful!
Preview URL: https://466b8a90.monorepo-eu0.pages.dev
Branch Preview URL: https://cl-actor.monorepo-eu0.pages.dev

View logs

@clabby
Copy link
Copy Markdown
Collaborator Author

clabby commented Feb 9, 2026

For the core control loop service, we effectively want to multiplex over several sources, mapping to the same event message type.

These sources can be of several types:

  • "Lane"s, which are known to be mpsc::Receivers that contain the actor's ingress type already.
  • "Source"s, which are opaque futures that are iteration-scoped. These futures resolve to a type T, and the user provides a function to map them to the actor's event message type.

The abstraction that we currently have is close, but it has some large problems:

  • How do we express "iteration-scoped future builders"?
    • The AsyncFn and AsyncFnMut traits are stable as of rust 1.85.0, but we currently cannot name their return types (nightly only.)
      • For context, these traits allow us to express Fn<'a>() -> Fut<'a>. See async_closures.
    • Writing generic code over the return type of an async future that borrows from the closure's inputs are not expressible in stable Rust. We might be able to work around this with Pin<Box<dyn Future<Output = T> + 'a>>, but this method incurs cost.
  • Even if we get the above to work, we run into another big problem: field projection.
    • If we had access to the nightly API and wrote future builder with GATs that looked like:
      /// A polled source armed for a single service-loop iteration.
      pub trait ArmedSource<I>: Send {
          /// Poll for one ingress message.
          ///
          /// - [`Poll::Ready`] with `Some(_)` yields an ingress event.
          /// - [`Poll::Ready`] with `None` indicates source exhaustion and stops the actor.
          /// - [`Poll::Pending`] indicates no event is ready.
          fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<I>>;
      }
      
      /// A source that can be armed once per service-loop iteration.
      pub trait Source<E, A, Init, I>: Send + 'static {
          /// Armed state for one iteration.
          type Armed<'a>: ArmedSource<I> + 'a
          where
              Self: 'a,
              A: 'a,
              E: 'a,
              Init: 'a;
      
          /// Arm this source for one iteration.
          fn arm<'a>(
              &'a mut self,
              actor: &'a mut A,
              context: &'a E,
              init: &'a mut Init,
          ) -> Self::Armed<'a>;
      }
      we still have a big problem, because a single source captures a potential mutable borrow over all sources. This means that one source that used the context for a sleep future would prevent a future that borrows from the actor's fields, for example.
    • This means we need a way to perform field projection (like the Rust compiler would handle for us in a concrete context).

Exploring some ways around this.


An idea came to mind which is a bit less "nice", but simple to reason about and sidesteps the issue of dealing with this in a generic context (taken from the v1 but adapted to be more useful.) We can add a function to Actor:

fn auxiliary(
    &mut self,
    context: &mut E,
    init: &mut Self::Init
) -> impl Future<Output = Option<Self::Ingress>> + Send;

that function could then be implemented by end-users with a select! body that receives from external futures and maps them to the Self::Ingress type. We could then add that future to the service's select. Might need to do a bit of work to ensure polling order, but theoretically a lot more simple.

@clabby clabby force-pushed the cl/actor branch 11 times, most recently from ed9a47a to f114bce Compare February 11, 2026 17:04
@clabby clabby marked this pull request as ready for review February 11, 2026 17:05
Comment thread actor/src/lib.rs
Comment thread actor/macros/src/ingress/mod.rs Outdated
Comment thread actor/src/service/driver.rs
Copy link
Copy Markdown
Member

@andresilva andresilva left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it would make sense to have another ingress type for synchronous read-only requests, maybe peek? E.g. in the counter example you could use an AtomicU64 for the state, in which case you can service multiple synchronous read-requests (or anything behind a RwLock that isn't inherently asynchronous). I'm thinking just as general purpose functionality, maybe we don't have enough usage to justify this pattern.

Comment thread actor/src/lib.rs Outdated
Comment thread actor/src/service/driver.rs Outdated
Comment thread actor/src/service/driver.rs Outdated
Comment thread actor/src/lib.rs
Comment thread actor/src/service/driver.rs Outdated
Comment thread actor/src/service/builder.rs Outdated
Comment thread actor/src/service/builder.rs Outdated
@clabby clabby force-pushed the cl/actor branch 2 times, most recently from fa19f3c to a3f3214 Compare February 11, 2026 21:24
@clabby clabby added this to Tracker Feb 11, 2026
@clabby clabby moved this to Ready for Review in Tracker Feb 11, 2026
Comment thread actor/README.md
Comment thread actor/README.md
Comment thread actor/README.md
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment thread actor/macros/src/ingress/parsing.rs
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 10, 2026

Codecov Report

❌ Patch coverage is 81.77005% with 241 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@a829e58). Learn more about missing BASE report.

Files with missing lines Patch % Lines
actor/macros/src/ingress/mod.rs 86.50% 48 Missing and 8 partials ⚠️
actor/src/service/builder.rs 74.50% 39 Missing ⚠️
actor/macros/src/ingress/parsing.rs 73.79% 26 Missing and 12 partials ⚠️
actor/src/service/driver.rs 86.98% 28 Missing and 10 partials ⚠️
actor/src/mailbox.rs 87.08% 26 Missing and 1 partial ⚠️
actor/src/service/types.rs 36.58% 26 Missing ⚠️
actor/src/lib.rs 73.43% 17 Missing ⚠️
@@           Coverage Diff           @@
##             main    #3077   +/-   ##
=======================================
  Coverage        ?   95.38%           
=======================================
  Files           ?      417           
  Lines           ?   144684           
  Branches        ?     3422           
=======================================
  Hits            ?   138004           
  Misses          ?     5553           
  Partials        ?     1127           
Files with missing lines Coverage Δ
actor/macros/src/lib.rs 100.00% <100.00%> (ø)
actor/src/lib.rs 73.43% <73.43%> (ø)
actor/src/service/types.rs 36.58% <36.58%> (ø)
actor/src/mailbox.rs 87.08% <87.08%> (ø)
actor/macros/src/ingress/parsing.rs 73.79% <73.79%> (ø)
actor/src/service/driver.rs 86.98% <86.98%> (ø)
actor/src/service/builder.rs 74.50% <74.50%> (ø)
actor/macros/src/ingress/mod.rs 86.50% <86.50%> (ø)

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a829e58...d27bb8d. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@clabby clabby linked an issue Mar 23, 2026 that may be closed by this pull request
@patrick-ogrady patrick-ogrady modified the milestones: v2026.4.0, v2026.4.1 Apr 13, 2026
@patrick-ogrady patrick-ogrady modified the milestones: v2026.5.0, v2026.6.0 May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Ready for Review

Development

Successfully merging this pull request may close these issues.

[runtime] Abstraction for pipelining read-only requests

3 participants